package com.ershijin.authserver.config;

import com.ershijin.authserver.handler.TokenIntrospectionSuccessHandler;
import com.ershijin.authserver.service.security.CustomizerJdbcOAuth2AuthorizationService;
import com.ershijin.authserver.jackson2.UsernamePasswordAuthenticationTokenModule;
import com.ershijin.authserver.util.OAuth2ConfigurerUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PasswordAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.web.SecurityFilterChain;

import java.util.Arrays;
import java.util.UUID;

@Configuration
public class AuthorizationServerConfig {

    /**
     * 个性化 Token
     * 自定义 OAuth2TokenContext 中的 OAuth 2.0 Token 属性
     */
    @Bean
    public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> tokenCustomizer() {
        return (context) -> {
            context.getClaims().claims(claims -> {
                claims.remove(IdTokenClaimNames.ISS);
                claims.remove(IdTokenClaimNames.AUD);
                claims.remove("nbf");
                claims.remove("jti");

                claims.put("user", context.getPrincipal().getPrincipal());
            });
            context.getClaims().claim("authorities", context.getPrincipal().getAuthorities().stream().map(GrantedAuthority::getAuthority).toArray());
        };
    }


    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);

        authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> tokenEndpoint.accessTokenRequestConverter(
                        new DelegatingAuthenticationConverter(Arrays.asList(
                                new OAuth2AuthorizationCodeAuthenticationConverter(),
                                new OAuth2RefreshTokenAuthenticationConverter(),
                                new OAuth2ClientCredentialsAuthenticationConverter(),
                                new OAuth2PasswordAuthenticationConverter())) // 自定义的 password 认证类型转换器
                )).tokenIntrospectionEndpoint(endpointConfigurer -> {
                    endpointConfigurer.introspectionResponseHandler(new TokenIntrospectionSuccessHandler());
        });


        SecurityFilterChain securityFilterChain = http.formLogin(Customizer.withDefaults()).build();

        OAuth2PasswordAuthenticationProvider oAuth2PasswordAuthenticationProvider = new OAuth2PasswordAuthenticationProvider(
                OAuth2ConfigurerUtils.getAuthorizationService(http),
                OAuth2ConfigurerUtils.getTokenGenerator(http),
                http.getSharedObject(AuthenticationManager.class)
        );
        http.authenticationProvider(oAuth2PasswordAuthenticationProvider);

        return securityFilterChain;
    }


    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("articles-client")
                .clientSecret("{noop}secret")
//          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethods(consumer -> {
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // 授权码方式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // 凭证式
                .authorizationGrantType(AuthorizationGrantType.IMPLICIT) // 隐藏式
                .redirectUri("http://app1:8080/login/oauth2/code/articles-client-oidc")
                .redirectUri("http://app1:8080/authorized")
                .scope(OidcScopes.OPENID)
                .scope("articles.read")
                .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build())
                .build();

        RegisteredClient registeredClient2 = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("articles-client2")
                .clientSecret("{noop}secret2")
                .clientAuthenticationMethods(consumer -> {
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // 授权码方式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // 凭证式
                .authorizationGrantType(AuthorizationGrantType.IMPLICIT) // 隐藏式
                .redirectUri("http://app2:8081/login/oauth2/code/articles-client-oidc")
                .redirectUri("http://app2:8081/authorized")
                .scope(OidcScopes.OPENID)
                .scope("articles.read")
                .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build())
                .build();

        JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        if (null == jdbcRegisteredClientRepository.findByClientId("articles-client")) {
            jdbcRegisteredClientRepository.save(registeredClient);
        }
        if (null == jdbcRegisteredClientRepository.findByClientId("articles-client2")) {
            jdbcRegisteredClientRepository.save(registeredClient2);
        }

        return jdbcRegisteredClientRepository;
    }

    /**
     * 保存授权信息，授权服务器给我们颁发来token，那我们肯定需要保存吧，由这个服务来保存
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        CustomizerJdbcOAuth2AuthorizationService authorizationService = new CustomizerJdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);

        class CustomOAuth2AuthorizationRowMapper extends CustomizerJdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
            public CustomOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
                super(registeredClientRepository);
                getObjectMapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
                getObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
                getObjectMapper().registerModule(new UsernamePasswordAuthenticationTokenModule());
                this.setLobHandler(new DefaultLobHandler());
            }
        }
        class CustomOAuth2AuthorizationParametersMapper extends CustomizerJdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
            public CustomOAuth2AuthorizationParametersMapper() {
                super();
                getObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
                getObjectMapper().registerModule(new UsernamePasswordAuthenticationTokenModule());

            }
        }

        CustomOAuth2AuthorizationRowMapper oAuth2AuthorizationRowMapper =
                new CustomOAuth2AuthorizationRowMapper(registeredClientRepository);

        authorizationService.setAuthorizationRowMapper(oAuth2AuthorizationRowMapper);
        authorizationService.setAuthorizationParametersMapper(new CustomOAuth2AuthorizationParametersMapper());
        return authorizationService;
    }

    /**
     * 如果是授权码的流程，可能客户端申请了多个权限，比如：获取用户信息，修改用户信息，此Service处理的是用户给这个客户端哪些权限，比如只给获取用户信息的权限
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 配置个端点的路径，如获取token、授权等
     *
     * @return
     */
    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder()
                .tokenEndpoint("/oauth2/token")
//                .issuer("http://auth-server:9000")
                .build();
    }
}